package de.jmda.mview.proj;

import java.io.File;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import javax.xml.bind.Unmarshaller;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;

import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import org.xml.sax.SAXException;

import de.jmda.mproc.swingui.JPanelVisualProcessorModel;
import de.jmda.util.fileset.FileSet;
import de.jmda.util.gui.swing.JPanelFavourites;
import de.jmda.util.jaxb.ConfigUtil;
import de.jmda.util.jaxb.ConfigUtil.NamespaceInfo;
import de.jmda.util.jaxb.SchemaGenerator;

public abstract class ProjectModelRepositoryStore
{
	private final static Logger LOGGER =
			Logger.getLogger(ProjectModelRepositoryStore.class);

	public final static String FILE_EXTENSION = ".mvpr.xml";

	private final static String PREFERRED_PREFIX = "jmda.mview";

	private final static File XML_SCHEMA_FILE_MAIN =
			new File(ProjectModelRepository.class.getSimpleName() + ".xsd");

	private final static File XML_SCHEMA_FILE_CORE =
			new File(ProjectModelRepository.class.getSimpleName() + "Core.xsd");

	private final static File XML_SCHEMA_FILE_CORE_SWINGUI =
			new File(ProjectModelRepository.class.getSimpleName() + "CoreSwingUI.xsd");

	private final static File XML_SCHEMA_FILE_GUI =
			new File(ProjectModelRepository.class.getSimpleName() + "GUI.xsd");

	private final static File XML_SCHEMA_FILE_TYPES =
			new File(ProjectModelRepository.class.getSimpleName() + "Types.xsd");

	private final static JAXBContext JAXB_CONTEXT;

	private final static ConfigUtil JAXB_CONFIG_UTIL;

	private final static SchemaGenerator SCHEMA_GENERATOR;

	private final static Schema SCHEMA;

	static
	{
		try
		{
			JAXB_CONTEXT =
//			    JAXBContext.newInstance
//			    (
//			    		      DesktopPane.JAXBModel.class.getPackage().getName()
////							+ ":" + Another.class.getPackage().getName()
//					);
			    JAXBContext.newInstance
					(
							new Class[]
							{
									// it seems to be sufficient to include the xml root element
									// class since all other reachable classes with JAXB
									// annotations will be included automatically
									ProjectModelRepository.class,
//									ProjectModel.class,
//									DiagramsModel.class,
//									DiagramGroup.class,
//									DesktopPane.JAXBModel.class,
//									DisplayOptionsModel.class,
//									DisplayOptionsModelContainer.class,
//									TypeShape.JAXBModel.class,
							}
					);
		}
		catch (JAXBException e)
		{
			throw new ExceptionInInitializerError(
			    "failure initialising JAXB context\n" + e);
		}

		JAXB_CONFIG_UTIL = new ConfigUtil();
		// this metadata entry will produce the central xsd file
		JAXB_CONFIG_UTIL.add(
				new NamespaceInfo(
						"",
						XML_SCHEMA_FILE_TYPES.getName(),
						PREFERRED_PREFIX));
		JAXB_CONFIG_UTIL.add(
				new NamespaceInfo(
						FileSet.XML_NAMESPACE,
						XML_SCHEMA_FILE_CORE.getName(),
						"jmda.core"));
		JAXB_CONFIG_UTIL.add(
				new NamespaceInfo(
						JPanelVisualProcessorModel.XML_NAMESPACE,
						XML_SCHEMA_FILE_CORE_SWINGUI.getName(),
						"jmda.core.swingui"));
		JAXB_CONFIG_UTIL.add(
				new NamespaceInfo(
						JPanelFavourites.XML_NAMESPACE,
						XML_SCHEMA_FILE_GUI.getName(),
						"jmda.gui"));
		JAXB_CONFIG_UTIL.add(
				new NamespaceInfo(
						ProjectModelRepository.XML_NAMESPACE,
						XML_SCHEMA_FILE_MAIN.getName(),
						PREFERRED_PREFIX));

		SCHEMA_GENERATOR =
				new SchemaGenerator(JAXB_CONTEXT, getNamespaceURISchemaFilenameMap());
		
		try
    {
	    SCHEMA_GENERATOR.generateSchema();
    }
    catch (IOException e)
    {
    	LOGGER.error("failure generating schema");
    }

		ClassLoader classloader = ProjectModelRepositoryStore.class.getClassLoader();

		List<Source> sources = new LinkedList<Source>();

		for (File schemafile : SCHEMA_GENERATOR.getGeneratedSchemafiles())
		{
			File target = new File("./bin/" + schemafile.getName());

			// copy schema files into classpath location to make it accessible via
			// classloader.getResourceAsStream
			try
			{
				FileUtils.copyFile(schemafile, target);
			}
			catch (IOException e)
			{
				LOGGER.error(
						"failure copying schema file [" + schemafile.getAbsolutePath() +
						"] to [" + target.getAbsolutePath() + "]");
      }

			sources.add(
					new StreamSource(
							classloader.getResourceAsStream(schemafile.getName())));
		}

		SchemaFactory schemaFactory = ConfigUtil.getSchemaFactory();

		Schema schema = null;

		try
	  {
			schema = schemaFactory.newSchema(sources.toArray(new Source[] {}));
	  }
	  catch (SAXException e)
	  {
	  	LOGGER.error(
	  			"failure creating schema, schema validation will not be available", e);
	  }

		SCHEMA = schema;
	}

	/**
	 * Loads a <code>ProjectModelRepository</code> instance from a default file.
	 *
	 * @return loaded <code>ProjectModelRepository</code>
	 */
	public static ProjectModelRepository load()
	{
		return load(ProjectModelRepository.createDefaultFile());
	}

	/**
	 * Stores a default <code>ProjectModelRepository</code> instance in a default
	 * file.
	 */
	public static void store()
	{
		store(createDefaultProjectModelRepository());
	}

	public static void store(ProjectModelRepository projectModelRepository)
	{
		store(projectModelRepository.getFile(), projectModelRepository);
	}

	public static ProjectModelRepository load(File file)
	{
		ProjectModelRepository result;

		FileReader fileReader = null;

		try
		{
			fileReader = new FileReader(file);

			result =
					(ProjectModelRepository) createUnmarshaller().unmarshal(fileReader);

			result.setFile(file);

			return result;
    }
    catch (IOException e)
    {
    	LOGGER.error(
    			"failure unmarshalling project repository from file " +
    			"[" + file.getAbsolutePath() + "]", e);
    }
    catch (JAXBException e)
    {
    	LOGGER.error(
    			"failure unmarshalling project repository from file " +
    			"[" + file.getAbsolutePath() + "]", e);
    }
		finally
		{
			if (fileReader != null)
			{
				try
				{
					fileReader.close();
				}
				catch (IOException e)
				{
					LOGGER.error(
							"failure closing file [" + file.getAbsolutePath() + "]", e);
				}
			}
		}

		return null;
	}

	private static void store(
			File file, ProjectModelRepository projectModelRepository)
	{
		FileWriter fileWriter = null;

		try
    {
			fileWriter = new FileWriter(file);
			createMarshaller().marshal(projectModelRepository, fileWriter);
			projectModelRepository.setFile(file);
    }
    catch (IOException e)
    {
    	LOGGER.error("failure marshalling project model repository", e);
    }
    catch (JAXBException e)
    {
    	LOGGER.error("failure marshalling project model repository", e);
    }
		finally
		{
			if (fileWriter != null)
			{
				try
				{
					fileWriter.flush();
					fileWriter.close();
				}
				catch (IOException e)
				{
					LOGGER.error(
							"failure closing file [" + file.getAbsolutePath() + "]", e);
				}
			}
		}
	}

	private static ProjectModelRepository createDefaultProjectModelRepository()
  {
	  return ProjectModelRepository.createDefaultProjectModelRepository();
  }

	/**
	 * The returned map can be used by {@link SchemaGenerator} to determine schema
	 * filenames for given namespaceURIs.
	 *
	 * @return
	 */
	private static Map<String, String> getNamespaceURISchemaFilenameMap()
	{
		Map<String, String> result = new HashMap<String, String>();

		for (String namespaceURI : JAXB_CONFIG_UTIL.getNamespaceURIs())
		{
			result.put(
					namespaceURI,
					JAXB_CONFIG_UTIL.getSchemaFilenameForNamespaceURI(namespaceURI));
		}
		
		return result;
	}
	
	private static Marshaller createMarshaller()
	{
		Marshaller result;
		
		try
		{
			result = JAXB_CONTEXT.createMarshaller();
			result.setSchema(SCHEMA);
		}
		catch (JAXBException e)
		{
			throw new IllegalStateException("failure creating marshaller", e);
		}

		JAXB_CONFIG_UTIL.activateFormattedOutput(result);
		JAXB_CONFIG_UTIL.activateNamespacePrefixMapping(result);
		
		return result;
	}
	
	private static Unmarshaller createUnmarshaller()
	{
		Unmarshaller result;

		try
		{
			result = JAXB_CONTEXT.createUnmarshaller();
			result.setSchema(SCHEMA);
		}
		catch (JAXBException e)
		{
			throw new IllegalStateException("failure creating unmarshaller", e);
		}
		
		return result;
	}
}